package com.cmc.cloud.cmclink.doc.service.impl.so;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cmc.cloud.cmclink.doc.api.document.emums.ContentChangeStatusEnum;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.api.document.rep.BlnoForecastReqDto;
import com.cmc.cloud.cmclink.doc.api.document.rep.BlnoSplitCombineRespDto;
import com.cmc.cloud.cmclink.doc.api.document.rep.SoRespDto;
import com.cmc.cloud.cmclink.doc.api.document.req.CheckSoCombineSiVO;
import com.cmc.cloud.cmclink.doc.api.document.req.so.SoBookingNoVO;
import com.cmc.cloud.cmclink.doc.api.document.req.so.SoForecastBaseVO;
import com.cmc.cloud.cmclink.doc.api.document.req.so.SoForecastRespVO;
import com.cmc.cloud.cmclink.doc.api.document.req.so.SoPageReqDto;
import com.cmc.cloud.cmclink.doc.configuration.CommonConfiguration;
import com.cmc.cloud.cmclink.doc.constants.BlnoConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoSoCombineSiErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.SoErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.SoSplitCombineErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.*;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.mapper.*;
import com.cmc.cloud.cmclink.doc.rpcservice.NucTransferService;
import com.cmc.cloud.cmclink.doc.service.manifest.ManifestService;
import com.cmc.cloud.cmclink.doc.service.si.VoyageLockService;
import com.cmc.cloud.cmclink.doc.service.so.BizSoService;
import com.cmc.cloud.cmclink.doc.service.so.SoAmendApplyService;
import com.cmc.cloud.cmclink.doc.service.so.SoService;
import com.cmc.cloud.cmclink.doc.service.so.SoSplitCombineService;
import com.cmc.cloud.cmclink.doc.util.so.CompareUtils;
import com.cmc.cloud.cmclink.doc.vo.sovo.attachmentvo.AttachmentRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.requirementsvo.RequirementsRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.socargovo.SoCargoRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.socargovo.SoCargoSplitCombineVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.socargovo.SoCargoSumVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.socontactvo.SoContactRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.soforecastvo.SoForecastSplitCombineVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sosplitcombinedetailvo.*;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovo.SoDetailRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovo.SoRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovslvoyvo.SoVslvoyBaseVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovslvoyvo.SoVslvoyRespVO;
import com.cmes.framework.common.exception.ErrorCode;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.CommonResult;
import com.cmes.framework.common.pojo.PageResult;
import com.cmes.framework.ex.Assertion;
import com.cmes.framework.mybatis.core.dataobject.BaseDO;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cmes.framework.security.core.util.SecurityFrameworkUtils;
import com.google.common.collect.Lists;
import groovy.lang.Tuple4;
import groovy.lang.Tuple5;
import groovy.lang.Tuple6;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.cmc.cloud.cmclink.doc.constants.CommonConstants.CNNGB;
import static com.cmc.cloud.cmclink.doc.constants.CommonConstants.CNSHA;
import static com.cmc.cloud.cmclink.doc.constants.errorconstant.SoErrorCodeConstants.*;
import static com.cmc.cloud.cmclink.doc.service.impl.si.BlnoServiceImpl.SEND_EDO_PORT;


/**
 * 订舱拆分合并记录表   Service 实现类
 *
 * @author 招商轮船-管理员
 */
@Service
@Validated
public class SoSplitCombineServiceImpl implements SoSplitCombineService {
    @Resource
    private BizSoService bizSoService;
    @Resource
    private SoService soService;
    @Resource
    @Lazy
    private SoSplitCombineDetailServiceImpl soSplitCombineDetailService;
    @Resource
    private SoSplitCombineMapper soSplitCombineMapper;
    @Resource
    private SoSplitCombineDetailMapper soSplitCombineDetailMapper;
    @Resource
    private SoMapper soMapper;
    @Resource
    private SoVslvoyMapper soVslvoyMapper;
    @Resource
    private SoCargoMapper soCargoMapper;
    @Resource
    private SoForecastMapper soForecastMapper;
    @Resource
    private SoContactMapper soContactMapper;
    @Resource
    private SoScnMapper soScnMapper;
    @Resource
    private AttachmentMapper attachmentMapper;
    @Resource
    private RequirementsMapper requirementsMapper;
    @Resource
    private BlnoMapper blnoMapper;
    @Resource
    private NucTransferService nucTransferService;
    @Resource
    ManifestService manifestService;
    @Resource
    SoAmendApplyService soAmendApplyService;
    @Resource
    private SoAmendApplyMapper soAmendApplyMapper;
    @Resource
    private CommonConfiguration configuration;
    @Resource
    private VoyageLockService voyageLockService;

    private static final DateTimeFormatter FORMATTER_AMEND = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    private static final String CUSTOMS_CUT_OFF = "customs_cut_off";

    /**
     * 网厅区分单证订舱数据
     */
    private static final String WEB = "WEB";

    @Override
    public SoDetailRespVO getSoDetailForSplitAndCombine(Long soId) {
        // 订舱数据 订舱已确认 并且有效
        SoDetailRespVO soDetail = getSoDetailForSplitAndCombineOne(soId);
        bizSoService.checkVoyageBookingTime(soDetail.getSo().getBookingVessel(), soDetail.getSo().getBookingVoyage(), soDetail.getSo().getPolCode(), CUSTOMS_CUT_OFF);
        return soDetail;
    }

    public SoDetailRespVO getSoDetailForSplitAndCombineOne(Long soId) {
        // 订舱数据 订舱已确认 并且有效
        SoDetailRespVO soDetail = bizSoService.getDetail(soId);
/*        if (!Objects.equals(String.valueOf(SecurityFrameworkUtils.getLoginUserId()), soDetail.getSo().getCreator())) {
            throw ServiceExceptionUtil.exception(SoErrorCodeConstants.SO_UPDATE_LIMIT);
        }*/
        if (!SoStatusEnum.BookingConfirmed.getValue().equals(soDetail.getSo().getSoStatus())) {
            throw ServiceExceptionUtil.exception(new ErrorCode(SoSplitCombineErrorCodeConstants.SO_SPLIT_COMBINE_STATUS_ERROR.getCode(), soDetail.getSo().getBookingNo() + SoSplitCombineErrorCodeConstants.SO_SPLIT_COMBINE_STATUS_ERROR.getMsg()));
        }
        if (!ValidStatusEnum.DEFAULT_VALID.getValue().equals(soDetail.getSo().getValidStatus())) {
            throw ServiceExceptionUtil.exception(new ErrorCode(SoSplitCombineErrorCodeConstants.SO_COMBINE_VALID_ERROR.getCode(), soDetail.getSo().getBookingNo() + SoSplitCombineErrorCodeConstants.SO_COMBINE_VALID_ERROR.getMsg()));
        }
        if (checkSoCargoIfHaveSpecial(soDetail)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(SoSplitCombineErrorCodeConstants.SO_COMBINE_SPECIAL_ERROR.getCode(), soDetail.getSo().getBookingNo() + SoSplitCombineErrorCodeConstants.SO_COMBINE_SPECIAL_ERROR.getMsg()));
        }
        if (hasSiByBlNo(soDetail.getSo().getBlNo())) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_SI_ERROR);
        }
        if (CollectionUtil.isNotEmpty(manifestService.findByBookingNos(Arrays.asList(soDetail.getSo().getBookingNo())))) {
            throw ServiceExceptionUtil.exception(SoErrorCodeConstants.SO_AMEND_MANIFEST_ERROR);
        }
        SoAmendApplyDO applyLast = soAmendApplyService.getByBookingNo(soDetail.getSo().getBookingNo(), Arrays.asList(ContentChangeStatusEnum.BOOKING_CHANGE_PROCESSING.getValue(), ContentChangeStatusEnum.BOOKING_CHANGE_AWAITING_MATERIAL_UPLOADED.getValue(), ContentChangeStatusEnum.BOOKING_CANCEL_PROCESSING.getValue()));
        if (applyLast != null) {
            throw ServiceExceptionUtil.exception(SoErrorCodeConstants.SO_UPDATE_CONFIRMED_DOU_ERROR);
        }
        return soDetail;
    }

    @Override
    public List<SoDetailRespVO> getSoDetailListForSplitAndCombine(List<Long> soIds) {
        List<SoDetailRespVO> soDetailList = new ArrayList<>();
        if (CollectionUtil.isEmpty(soIds)) {
            return soDetailList;
        }
        if (soIds.size() < 1) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_EMPTY);
        }
        soIds.forEach(id -> {
            soDetailList.add(getSoDetailForSplitAndCombineOne(id));
        });
        SoRespVO soFirst = soDetailList.get(0).getSo();
        bizSoService.checkVoyageBookingTime(soFirst.getBookingVessel(), soFirst.getBookingVoyage(), soFirst.getPolCode(), CUSTOMS_CUT_OFF);
        // 检查数据是否相同
        for (int i = 0; i < soDetailList.size() - 1; i++) {
            SoDetailRespVO nowDetail = soDetailList.get(i);
            SoDetailRespVO nextDetail = soDetailList.get(i + 1);
            String titleInfo = nowDetail.getSo().getBookingNo() + "," + nextDetail.getSo().getBookingNo() + " ";
            // 检查SO数据是否相同
            String checkSo = CompareUtils.compareByFieldsString(nowDetail.getSo(), nextDetail.getSo(), SoRespVO.getMultiFieldComparatorCombine());
            if (StringUtils.isNotEmpty(checkSo)) {
                Assertion.assertTrue(false).raise0(SoSplitCombineErrorCodeConstants.SO_COMBINE_INFO_ERROR.getCode(), titleInfo + "Basic Information");
            }
            // 检查航线是否相同
            if (nowDetail.getSoVslvoyList().size() != nextDetail.getSoVslvoyList().size()) {
                Assertion.assertTrue(false).raise0(SoSplitCombineErrorCodeConstants.SO_COMBINE_INFO_ERROR.getCode(), titleInfo + "Voyage Route");
            }
            for (int j = 0; j < nowDetail.getSoVslvoyList().size(); j++) {
                SoVslvoyRespVO nowVslvoy = nowDetail.getSoVslvoyList().get(j);
                SoVslvoyRespVO nextVslvoy = nextDetail.getSoVslvoyList().get(j);
                String checkVslvoy = CompareUtils.compareByFieldsString(nowVslvoy, nextVslvoy, SoVslvoyBaseVO.getMultiFieldComparator());
                if (StringUtils.isNotEmpty(checkVslvoy)) {
                    Assertion.assertTrue(false).raise0(SoSplitCombineErrorCodeConstants.SO_COMBINE_INFO_ERROR.getCode(), titleInfo + "Voyage Route");
                }
            }
            if (CollectionUtil.isEmpty(nowDetail.getSoCargoList()) || CollectionUtil.isEmpty(nextDetail.getSoCargoList())) {
                throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_INFO_ERROR);
            }
            // 匹配货物信息是否相同
            String compareResultMain = CompareUtils.compareByFieldsString(nowDetail.getSoCargoList().get(0), nextDetail.getSoCargoList().get(0), SoCargoRespVO.getMultiFieldComparatorMain());
            if (StringUtils.isNotEmpty(compareResultMain)) {
                Assertion.assertTrue(false).raise0(SoSplitCombineErrorCodeConstants.SO_COMBINE_INFO_ERROR.getCode(), titleInfo + "Cargo");
            }
        }//匹配
        return soDetailList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "splitCombineUnique", keys = {"'createSoSplit'", "#userId"})
    public void createSoSplit(SoSplitCreateReqVO createReqVO, String userId) {
        if (CollectionUtil.isEmpty(createReqVO.getSplitDetailList())) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_SPLIT_EMPTY);
        }
        // 数据复制
        SoDetailRespVO oldSo = getSoDetailForSplitAndCombine(createReqVO.getOldSoId());
        //检查数据 数据有效性校验
        Map<String, SoForecastRespVO> oldSoForecastMap = oldSo.getSoForecastList().stream().collect(Collectors.toMap(e -> e.getCntrType() + "_" + e.getCntrSize(), e -> e));
        List<SoSplitCombineInfoReqVO> splitDetailList = createReqVO.getSplitDetailList();
        AtomicReference<Integer> bookingNoCount = new AtomicReference<>(0);
        splitDetailList.forEach(e -> {
            if (CollectionUtil.isEmpty(e.getSoForecastList()) || CollectionUtil.isEmpty(e.getSoCargoList())) {
                throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_SPLIT_EMPTY);
            }
            if (StringUtils.isNotEmpty(e.getBookingNo())) {
                if (!Objects.equals(oldSo.getSo().getBookingNo(), e.getBookingNo())) {
                    throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_SPLIT_BOOKING);
                }
                bookingNoCount.getAndSet(bookingNoCount.get() + 1);
                if (bookingNoCount.get() > 1) {
                    throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_SPLIT_BOOKING);
                }
            }
            e.getSoForecastList().forEach(f -> {
                String keyForecast = f.getCntrType() + "_" + f.getCntrSize();
                SoForecastRespVO forecast = oldSoForecastMap.get(keyForecast);
                if (forecast == null) {
                    throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_SPLIT_FORECAST_ERROR);
                }
                forecast.setCntrQuantity(forecast.getCntrQuantity() - f.getCntrQuantity());
            });
        });
        // 检查数量是否一致
        long noMatch = oldSoForecastMap.values().stream().filter(e -> e.getCntrQuantity() != 0).count();
        if (noMatch > 0) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_SPLIT_FORECAST_NUM_ERROR);
        }
        SoSplitCombineDO newSplit = new SoSplitCombineDO();
        //OPERATION20250515093623
        newSplit.setOperationType(SoSplitCombineEnum.SPLIT.getValue());
        newSplit.setOperationNo("OPERATION" + LocalDateTime.now().format(FORMATTER_AMEND));
        newSplit.setApproveStatus("Approved");
        newSplit.setApplyTime(LocalDateTime.now());
        newSplit.setApplyUser(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        newSplit.setApproveTime(LocalDateTime.now());
        newSplit.setApplyUser(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        soSplitCombineMapper.insert(newSplit);

        List<SoSplitCombineDetailDO> splitList = new ArrayList<>();
        // 用户录入的拆分数据 数据重置
        splitDetailList.forEach(e -> {
            List<SoForecastRespVO> splitCombineSoForecastList = new ArrayList<>();
            List<SoCargoRespVO> splitCombineSoCargoList = new ArrayList<>();
            //计算货物
            BigDecimal grossWeight = BigDecimal.ZERO;
            for (SoForecastSplitCombineVO f : e.getSoForecastList()) {
                String keyForecast = f.getCntrType() + "_" + f.getCntrSize();
                SoForecastRespVO forecast = oldSoForecastMap.get(keyForecast);
                SoForecastRespVO forecastNew = SoForecastConvert.INSTANCE.convert(forecast);
                forecastNew.setCntrQuantity(f.getCntrQuantity());
                grossWeight = grossWeight.add(forecastNew.getPerCntrWgt().multiply(new BigDecimal(forecastNew.getCntrQuantity())));
                splitCombineSoForecastList.add(forecastNew);
            }
            // 货物按照单个计算
            SoCargoSplitCombineVO c = e.getSoCargoList().get(0);
            SoCargoRespVO cargo = oldSo.getSoCargoList().get(0);
            cargo.setCommodityCn(c.getCommodityCn());
            cargo.setCommodityEn(c.getCommodityEn());
            // 重量重新计算
            cargo.setGrossWeight(grossWeight);
            splitCombineSoCargoList.add(cargo);
            oldSo.setSplitCombineSoForecastList(splitCombineSoForecastList);
            oldSo.setSplitCombineSoCargoList(splitCombineSoCargoList);

            // 网厅referenceNo有更新需求 前端传啥就是啥 单证逻辑不变
            if(Objects.equals(WEB, oldSo.getSo().getDataSource())){
                oldSo.getSo().setReferenceNo(e.getReferenceNo());
            }
            SoDO newSo = splitCombineToCreate(oldSo, e.getBookingNo(), true);
            //记录
            SoSplitCombineDetailDO detail = new SoSplitCombineDetailDO();
            detail.setOperationId(newSplit.getId());
            detail.setSoId(createReqVO.getOldSoId());
            detail.setNewSoId(newSo.getId());
            splitList.add(detail);
        });
        soSplitCombineDetailMapper.insertBatch(splitList);
        //当前数据变为无效
        soService.updateValidStatus(Arrays.asList(createReqVO.getOldSoId()), ValidStatusEnum.INVALID.getValue());
    }

    @Transactional(rollbackFor = Exception.class)
    public SoDO splitCombineToCreate(SoDetailRespVO oldSo, String bookingNo, Boolean ifSplit) {
        SoDO so = SoConvert.INSTANCE.convert(oldSo.getSo());
        //重新设置 订舱号和提单号
        if (StringUtils.isEmpty(bookingNo)) {
            so.setBookingNo(null);
            so.setBlNo(null);
            bizSoService.getBookingNoAndBlNoBySo(so);
        }
        so.setSplitCombine(ifSplit ? SoSplitCombineEnum.SPLIT.getValue() : SoSplitCombineEnum.COMBINE.getValue());
        List<SoVslvoyDO> soVslvoyList = SoVslvoyConvert.INSTANCE.convertDoList(oldSo.getSoVslvoyList());
        List<SoCargoDO> soCargoList = SoCargoConvert.INSTANCE.convertDoList(oldSo.getSplitCombineSoCargoList());
        List<SoForecastDO> soForecastList = SoForecastConvert.INSTANCE.convertDoList(oldSo.getSplitCombineSoForecastList());
        SoScnDO soScn = SoScnConvert.INSTANCE.convert(oldSo.getSoScn());
        List<SoContactDO> soContactList = SoContactConvert.INSTANCE.convertDoList(oldSo.getSoContactList());
        List<AttachmentDO> attachmentList = AttachmentConvert.INSTANCE.convertDoList(oldSo.getAttachmentList());
        List<RequirementsDO> requirementsList = RequirementsConvert.INSTANCE.convertDoList(oldSo.getRequirementsList());
        so.setId(null);
        soMapper.insert(so);
        //清空soId绑定关系,设置默认状态
        soVslvoyList.forEach(e -> {
            e.setId(null);
            e.setSoId(so.getId());
            reSetNull(e);
        });
        soCargoList.forEach(e -> {
            e.setId(null);
            e.setSoId(so.getId());
            reSetNull(e);
        });
        soForecastList.forEach(e -> {
            e.setId(null);
            e.setSoId(so.getId());
            reSetNull(e);
        });
        if (soScn != null) {
            soScn.setId(null);
            soScn.setSoId(so.getId());
            reSetNull(soScn);
        }
        soContactList.forEach(e -> {
            e.setId(null);
            e.setSoId(so.getId());
            reSetNull(e);
        });
        attachmentList.forEach(e -> {
            e.setId(null);
            e.setReferenceId(so.getId());
            reSetNull(e);
        });
        requirementsList.forEach(e -> {
            e.setId(null);
            e.setReferenceId(so.getId());
            reSetNull(e);
        });
        // 批量插入
        soVslvoyMapper.insertBatch(soVslvoyList);
        soCargoMapper.insertBatch(soCargoList);
        soForecastMapper.insertBatch(soForecastList);
        soScnMapper.insert(soScn);
        soContactMapper.insertBatch(soContactList);
        attachmentMapper.insertBatch(attachmentList);
        requirementsMapper.insertBatch(requirementsList);
        return so;
    }


    public void reSetNull(BaseDO baseInfo) {
        baseInfo.setDeleted(null);
        baseInfo.setCreator(null);
        baseInfo.setCreateTime(null);
        baseInfo.setUpdater(null);
        baseInfo.setUpdateTime(null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "splitCombineUnique", keys = {"'createSoCombine'", "#userId"})
    public void createSoCombine(SoCombineCreateReqVO updateReqVO, String userId) {
        List<SoDetailRespVO> oldDetaillist = getSoDetailListForSplitAndCombine(updateReqVO.getOldSoIdList());
        SoSplitCombineInfoReqVO combineDetail = updateReqVO.getCombineDetail();
        if (CollectionUtil.isEmpty(combineDetail.getSoForecastList()) || CollectionUtil.isEmpty(combineDetail.getSoCargoList())) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_INFO_EMPTY);
        }
        Map<String, SoForecastRespVO> splitCombineSoForecastMap = new LinkedHashMap<>(16);
        Map<String, SoForecastSplitCombineVO> newSoForecastMap = combineDetail.getSoForecastList().stream().collect(Collectors.toMap(e -> e.getCntrType() + "_" + e.getCntrSize(), e -> e));
        // 是否有默认选中的
        SoCargoSplitCombineVO cargoCombine = combineDetail.getSoCargoList().get(0);
        if (cargoCombine.getId() == null) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_SUM_CARGO_ERROR);
        }
        SoCargoRespVO chooseCargo = null;
        SoDetailRespVO chooseSo = null;
        SoDetailRespVO combineInfo = null;
        for (SoDetailRespVO e : oldDetaillist) {
            if (StringUtils.isNotEmpty(combineDetail.getBookingNo()) && Objects.equals(combineDetail.getBookingNo(), e.getSo().getBookingNo())) {
                chooseSo = e;
            }
            e.getSoForecastList().forEach(f -> {
                String keyForecast = f.getCntrType() + "_" + f.getCntrSize();
                SoForecastSplitCombineVO forecastCombine = newSoForecastMap.get(keyForecast);
                if (forecastCombine == null) {
                    throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_INFO_EMPTY);
                }

                //导入到新建数据中
                SoForecastRespVO copyForecast = SoForecastConvert.INSTANCE.convert(f);
                copyForecast.setCntrQuantity(forecastCombine.getCntrQuantity());
                SoForecastRespVO newForecast = splitCombineSoForecastMap.get(keyForecast);
                if (newForecast == null) {
                    splitCombineSoForecastMap.put(keyForecast, copyForecast);
                } else {
                    if (copyForecast.getPerCntrWgt().compareTo(newForecast.getPerCntrWgt()) > 0) {
                        copyForecast.setCntrQuantity(newForecast.getCntrQuantity());
                        splitCombineSoForecastMap.put(keyForecast, copyForecast);
                    }
                }
                //计算总数
                forecastCombine.setCntrQuantity(forecastCombine.getCntrQuantity() - f.getCntrQuantity());
            });
            for (SoCargoRespVO c : e.getSoCargoList()) {
                if (c.getId().equals(cargoCombine.getId())) {
                    chooseCargo = c;
                    chooseCargo.setCommodityCn(cargoCombine.getCommodityCn());
                    chooseCargo.setCommodityEn(cargoCombine.getCommodityEn());
                }
            }
            if (combineInfo == null) {
                combineInfo = new SoDetailRespVO();
                combineInfo.setSo(e.getSo());
                combineInfo.setSoScn(e.getSoScn());
                combineInfo.setSoCargoList(e.getSoCargoList());
                combineInfo.setSoVslvoyList(e.getSoVslvoyList());
                combineInfo.setRequirementsList(new ArrayList<>(16));
                combineInfo.setAttachmentList(new ArrayList<>(16));
                combineInfo.setSoContactList(new ArrayList<>(16));
            }
            //其他信息拼接
            combineInfo.getRequirementsList().addAll(e.getRequirementsList());
            combineInfo.getAttachmentList().addAll(e.getAttachmentList());
            combineInfo.getSoContactList().addAll(e.getSoContactList());

        }
        // 合并数据检查
        long noMatch = combineDetail.getSoForecastList().stream().filter(e -> e.getCntrQuantity() != 0).count();
        if (noMatch > 0) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_SUM_ERROR);
        }
        if (StringUtils.isNotEmpty(combineDetail.getBookingNo())) {
            if (chooseSo == null) {
                throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_SUM_ERROR);
            } else {
                combineInfo.setSo(chooseSo.getSo());
                combineInfo.setSoScn(chooseSo.getSoScn());
            }
        }
        if (chooseCargo == null) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_COMBINE_SUM_CARGO_ERROR);
        }
        BigDecimal grossWeight = BigDecimal.ZERO;
        for (SoForecastRespVO f : splitCombineSoForecastMap.values()) {
            grossWeight = grossWeight.add(f.getPerCntrWgt().multiply(new BigDecimal(f.getCntrQuantity())));
        }
        chooseCargo.setGrossWeight(grossWeight);
        combineInfo.setSplitCombineSoCargoList(Arrays.asList(chooseCargo));
        combineInfo.setSplitCombineSoForecastList(splitCombineSoForecastMap.values().stream().collect(Collectors.toList()));
        //数据去重处理
        removeSameInfo(combineInfo);
        //数据保存 合并的时候无需更改referenceNo
        SoDO newSo = splitCombineToCreate(combineInfo, combineInfo.getSo().getBookingNo(), false);
        //其他数据标记无效 记录申请记录
        SoSplitCombineDO newCobine = new SoSplitCombineDO();
        //OPERATION20250515093623
        newCobine.setOperationType(SoSplitCombineEnum.COMBINE.getValue());
        newCobine.setOperationNo("OPERATION" + LocalDateTime.now().format(FORMATTER_AMEND));
        newCobine.setApproveStatus("Approved");
        newCobine.setApplyTime(LocalDateTime.now());
        newCobine.setApplyUser(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        newCobine.setApproveTime(LocalDateTime.now());
        newCobine.setApplyUser(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        soSplitCombineMapper.insert(newCobine);

        List<SoSplitCombineDetailDO> combineList = new ArrayList<>(16);
        List<Long> oldSoIds = new ArrayList<>(16);
        oldDetaillist.forEach(e -> {
            SoSplitCombineDetailDO detail = new SoSplitCombineDetailDO();
            detail.setOperationId(newCobine.getId());
            detail.setSoId(e.getSo().getId());
            detail.setNewSoId(newSo.getId());
            combineList.add(detail);
            oldSoIds.add(e.getSo().getId());
        });
        soSplitCombineDetailMapper.insertBatch(combineList);
        //当前数据变为无效
        soService.updateValidStatus(oldSoIds, ValidStatusEnum.INVALID.getValue());

    }

    public void removeSameInfo(SoDetailRespVO combineInfo) {
        //去除重复的合并信息
        if (CollectionUtil.isNotEmpty(combineInfo.getRequirementsList())) {
            Map<Tuple4, RequirementsRespVO> requirementsMap = new LinkedHashMap<>();
            combineInfo.getRequirementsList().forEach(e -> {
                Tuple4 key = new Tuple4(e.getReferenceType(), e.getRequireTypeCode(), e.getRequireCode(), e.getRequireRemark());
                if (requirementsMap.get(key) == null) {
                    requirementsMap.put(key, e);
                }
            });
            combineInfo.setRequirementsList(Lists.newArrayList(requirementsMap.values()));
        }
        if (CollectionUtil.isNotEmpty(combineInfo.getAttachmentList())) {
            Map<Tuple5, AttachmentRespVO> attachmentMap = new LinkedHashMap<>();
            combineInfo.getAttachmentList().forEach(e -> {
                Tuple5 key = new Tuple5(e.getReferenceType(), e.getAttachTypeCode(), e.getAttachName(), e.getFileFormat(), e.getFileId());
                if (attachmentMap.get(key) == null) {
                    attachmentMap.put(key, e);
                }
            });
            combineInfo.setAttachmentList(Lists.newArrayList(attachmentMap.values()));
        }

        if (CollectionUtil.isNotEmpty(combineInfo.getSoContactList())) {
            Map<Tuple6, SoContactRespVO> contactMap = new LinkedHashMap<>();
            combineInfo.getSoContactList().forEach(e -> {
                Tuple6 key = new Tuple6(e.getContactType(), e.getContactPerson(), e.getContactTelephone(), e.getContactEmail(), e.getContactAddress(), e.getContactFax());
                if (contactMap.get(key) == null) {
                    contactMap.put(key, e);
                }
            });
            combineInfo.setSoContactList(Lists.newArrayList(contactMap.values()));
        }
    }

    @Override
    public void deleteSoSplitCombine(Long id) {
        // 校验存在
        validateSoSplitCombineExists(id);
        // 删除
        soSplitCombineMapper.deleteById(id);
    }

    private void validateSoSplitCombineExists(Long id) {
        if (soSplitCombineMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_SPLIT_COMBINE_NOT_EXISTS);
        }
    }

    @Override
    public SoSplitCombineAllRespVO getSoSplitCombine(Long splitCombineId) {
        SoSplitCombineAllRespVO allInfo = new SoSplitCombineAllRespVO();
        SoSplitCombineDO soSplitCombine = soSplitCombineMapper.selectById(splitCombineId);
        if (soSplitCombine == null) {
            throw ServiceExceptionUtil.exception(SoSplitCombineErrorCodeConstants.SO_SPLIT_COMBINE_NOT_EXISTS);
        }
        List<Long> userIds = new ArrayList<>();
        if (StringUtils.isNotEmpty(soSplitCombine.getApplyUser())) {
            userIds.add(Long.valueOf(soSplitCombine.getApplyUser()));
        }
        if (StringUtils.isNotEmpty(soSplitCombine.getApproveUser())) {
            userIds.add(Long.valueOf(soSplitCombine.getApproveUser()));
        }
        if (CollectionUtil.isNotEmpty(userIds)) {
            Map<String, String> nameMap = nucTransferService.getUserProfileByIdMap(userIds);
            if (StringUtils.isNotEmpty(soSplitCombine.getApplyUser())) {
                soSplitCombine.setApplyUser(nameMap.get(soSplitCombine.getApplyUser()));
            }
            if (StringUtils.isNotEmpty(soSplitCombine.getApproveUser())) {
                soSplitCombine.setApproveUser(nameMap.get(soSplitCombine.getApproveUser()));
            }
        }
        allInfo.setSoSplitCombine(SoSplitCombineConvert.INSTANCE.convert(soSplitCombine));
        allInfo.setSoSplitCombineDetailList(soSplitCombineDetailService.getDetailListByOperationId(splitCombineId));
        List<Long> oldSoIds = allInfo.getSoSplitCombineDetailList().stream().map(e -> e.getSoId()).collect(Collectors.toList());
        List<Long> newSoIds = allInfo.getSoSplitCombineDetailList().stream().map(e -> e.getNewSoId()).collect(Collectors.toList());
        if (Objects.equals(SoSplitCombineEnum.COMBINE.getValue(), soSplitCombine.getOperationType())) {
            // 多个old 一个new
            List<SoDetailRespVO> combineOldSoDetail = new ArrayList<>();
            oldSoIds.forEach(soId -> {
                combineOldSoDetail.add(bizSoService.getDetail(soId));
            });
            allInfo.setCombineOldSoDetail(combineOldSoDetail);
            allInfo.setCombineNewSoDetail(bizSoService.getDetail(newSoIds.get(0)));
        }
        if (Objects.equals(SoSplitCombineEnum.SPLIT.getValue(), soSplitCombine.getOperationType())) {
            // 一个old 多个new
            List<SoDetailRespVO> splitNewSoDetailList = new ArrayList<>();
            newSoIds.forEach(soId -> {
                splitNewSoDetailList.add(bizSoService.getDetail(soId));
            });
            allInfo.setSplitOldSoDetail(bizSoService.getDetail(oldSoIds.get(0)));
            allInfo.setSplitNewSoDetailList(splitNewSoDetailList);
        }
        return allInfo;
    }

    @Override
    public List<SoSplitCombineDO> getSoSplitCombineList(Collection<Long> ids) {
        return soSplitCombineMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<SoSplitCombinePageRespVO> getSoSplitCombinePage(SoSplitCombinePageReqVO reqVO) {
        IPage<Long> page = Page.of(reqVO.getPageNo(), reqVO.getPageSize());
        if (reqVO.getApplyTimeEnd() != null) {
            reqVO.setApplyTimeEnd(reqVO.getApplyTimeEnd().plusDays(1));
        }
        IPage<Long> result = soSplitCombineMapper.selectPageList(page, reqVO);
        if (result.getTotal() > 0) {
            List<SoSplitCombinePageRespVO> list = soSplitCombineMapper.selectListByIds(result.getRecords());
            return new PageResult<SoSplitCombinePageRespVO>(list, result.getTotal());
        }
        return new PageResult<SoSplitCombinePageRespVO>(new ArrayList<SoSplitCombinePageRespVO>(), 0L);
    }

    @Override
    public List<SoBookingNoVO> getCombineBookingNoList(String bookingNo, String csmCode) {
        // 查询主订舱信息
        SoDO soMain = soMapper.selectOne(new LambdaQueryWrapperX<SoDO>().eq(SoDO::getBookingNo, bookingNo)
                .eq(SoDO::getBookingParty,csmCode)
                .eq(SoDO::getValidStatus, ValidStatusEnum.DEFAULT_VALID.getValue()));
        if(Objects.isNull(soMain)){
            throw ServiceExceptionUtil.exception(SO_NOT_EXISTS);
        }
        if(!Objects.equals(soMain.getSoStatus(),SoStatusEnum.BookingConfirmed.getValue())) {
            throw ServiceExceptionUtil.exception(SO_MAIN_STATUS_ERROR);
        }
        // 查询对应订舱数据
        SoPageReqDto soPageReqDto = this.convertSoPageReqDto(csmCode,soMain);
        IPage<SoDO> page = Page.of(soPageReqDto.getPageNo(), soPageReqDto.getPageSize());
        IPage<SoDO> pageResult = soMapper.rpcQuerySoInfo(page, soPageReqDto);
        if(pageResult.getTotal()==0) {
            return new ArrayList<>();
        }
        List<SoDO> soList = pageResult.getRecords().stream().filter(soDO -> !soDO.getBookingNo().equals(soMain.getBookingNo())).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(soList)){
            return new ArrayList<>();
        }
        //查询是否有过提单
        List<BlnoDO> blnoList = blnoMapper.selectList(BlnoDO::getBookingNo,soList.stream().map(SoDO::getBookingNo).collect(Collectors.toList()));
        List<String> blnoBookingNoList = blnoList.stream().map(BlnoDO::getBookingNo).collect(Collectors.toList());
        soList = soList.stream().filter(soDO -> !blnoBookingNoList.contains(soDO.getBookingNo())).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(soList)){
            return new ArrayList<>();
        }
        //查询货物信息
        List<Long> soIds = soList.stream().map(SoDO::getId).collect(Collectors.toList());
        List<SoCargoDO> soCargoDos = soCargoMapper.selectList(new LambdaQueryWrapper<SoCargoDO>().in(SoCargoDO::getSoId, soIds));
        // 查询预配箱信息
        List<SoForecastBaseVO> soForecastDos = this.getSoForecast(soIds);
        List<SoAmendApplyDO> soAmendApplyDos = soAmendApplyMapper.selectList(new LambdaQueryWrapperX<SoAmendApplyDO>()
                .in(SoAmendApplyDO::getBookingNo, soList.stream().map(SoDO::getBookingNo).collect(Collectors.toList()))
                .in(SoAmendApplyDO::getSoAmendStatus,
                        Arrays.asList(ContentChangeStatusEnum.BOOKING_CHANGE_PROCESSING.getValue(),
                                ContentChangeStatusEnum.BOOKING_CHANGE_AWAITING_MATERIAL_UPLOADED.getValue(),
                                ContentChangeStatusEnum.BOOKING_CANCEL_PROCESSING.getValue())));
        List<String> soAmendApplyBookingNos = soAmendApplyDos.stream().map(SoAmendApplyDO::getBookingNo).collect(Collectors.toList());
        List<SoBookingNoVO> soRespDtoList = new ArrayList<>();
        soList.forEach(soDO -> {
            if(checkCargo(soCargoDos,soDO.getId())
                    && !soAmendApplyBookingNos.contains(soDO.getBookingNo())
                    && !checkPortAgent(soDO.getPolCode(),soDO.getPorCode(),soDO.getTopAgent())){
                SoBookingNoVO soBookingNoVO = new SoBookingNoVO();
                soBookingNoVO.setBookingNo(soDO.getBookingNo());
                soBookingNoVO.setSoId(soDO.getId());
                soBookingNoVO.setSoForecastList(soForecastDos.stream().filter(a->a.getSoId().compareTo(soDO.getId())==0).collect(Collectors.toList()));
                soRespDtoList.add(soBookingNoVO);
            }
        });
        return soRespDtoList;
    }

    @Override
    public List<CheckSoCombineSiVO> checkSoCombineSi(BlnoForecastReqDto dto) {
        String cmsCode = dto.getCsmCode();
        List<CheckSoCombineSiVO> rntList = new ArrayList<>();
        // 查询bookingNo对应的soId
        List<String> bookingNos = new ArrayList<>();
        bookingNos.add(dto.getBookingNo());
        if(CollectionUtils.isNotEmpty(bookingNos)){
            bookingNos.addAll(dto.getBookingNos());
        }
        List<SoDO> soList = soMapper.selectList(new LambdaQueryWrapperX<SoDO>()
                .eq(SoDO::getBookingParty,cmsCode)
                .eq(SoDO::getValidStatus, ValidStatusEnum.DEFAULT_VALID.getValue()).in(SoDO::getBookingNo,bookingNos));
        if(CollectionUtil.isEmpty(soList)){
            throw ServiceExceptionUtil.exception(SO_NOT_EXISTS);
        }
        Optional<SoDO> optional = soList.stream().filter(soDO -> Objects.equals(soDO.getBookingNo(),dto.getBookingNo())).findFirst();
        if(!optional.isPresent()){
            throw ServiceExceptionUtil.exception(SO_NOT_EXISTS);
        }
        SoDO soMain = optional.get();
        // 判断是否锁船
        VoyageLockDO voyageLockDO = voyageLockService.findVoyageLock(soMain.getBookingVessel(),soMain.getBookingVoyage(),soMain.getPolCode(),ExImTypeEnum.EX.getValue());
        if(Objects.nonNull(voyageLockDO) && voyageLockDO.getVoyageLockFlag()){
            throw ServiceExceptionUtil.exception(WEB_SO_VOYAGE_LOCK_ERROR);
        }
        if(checkPortAgent(soMain.getPolCode(),soMain.getPorCode(),soMain.getTopAgent())){
            throw ServiceExceptionUtil.exception(WEB_SO_MANIFEST_ERROR);
        }
        Map<String,Long> soMap = soList.stream().collect(Collectors.toMap(SoDO::getBookingNo,SoDO::getId));
        List<Long> soIds = soList.stream().map(SoDO::getId).collect(Collectors.toList());
        List<SoForecastBaseVO> soForecastDos = this.getSoForecast(soIds);
        List<BlnoDO> blnoDos = blnoMapper.selectList(new LambdaQueryWrapperX<BlnoDO>()
                .inIfPresent(BlnoDO::getBookingNo, bookingNos)
                .eq(BlnoDO::getPodBlFlag, true)
                .eq(BlnoDO::getValid, SiValidEnum.VALID.getValue()));
        // 判断是否提单
        if (blnoDos.stream().anyMatch(a -> Objects.equals(a.getBookingNo(), soMain.getBookingNo()))) {
            throw ServiceExceptionUtil.exception(BlnoSoCombineSiErrorCodeConstants.SO_SI_EXISTED);
        }
        List<SoAmendApplyDO> soAmendApplyDos = soAmendApplyMapper.selectList(new LambdaQueryWrapperX<SoAmendApplyDO>()
                .inIfPresent(SoAmendApplyDO::getBookingNo, bookingNos)
                .inIfPresent(SoAmendApplyDO::getSoAmendStatus,
                        Arrays.asList(ContentChangeStatusEnum.BOOKING_CHANGE_PROCESSING.getValue(),
                                ContentChangeStatusEnum.BOOKING_CHANGE_AWAITING_MATERIAL_UPLOADED.getValue(),
                                ContentChangeStatusEnum.BOOKING_CANCEL_PROCESSING.getValue())));
        List<String> soAmendApplyBookingNos = soAmendApplyDos.stream().map(SoAmendApplyDO::getBookingNo).collect(Collectors.toList());
        if(soAmendApplyBookingNos.contains(soMain.getBookingNo())){
            throw ServiceExceptionUtil.exception(BlnoSoCombineSiErrorCodeConstants.SO_STATUS_AMEND);
        }
        List<SoCargoDO> soCargoDos = soCargoMapper.selectList(new LambdaQueryWrapper<SoCargoDO>().in(SoCargoDO::getSoId, soIds));
        bookingNos.forEach(bookingNo -> {
            CheckSoCombineSiVO checkSo = new CheckSoCombineSiVO();
            checkSo.setBookingNo(bookingNo);
            List<String> messageList = new ArrayList<>();
            if(Objects.isNull(soMap.get(bookingNo))){
                // 判断是否存在so
                messageList.add(SO_NOT_EXISTS.getMsg());
            } else {
                checkSo.setSoId(soMap.get(bookingNo));
                checkSo.setSoForecastList(soForecastDos.stream().filter(a -> a.getSoId().compareTo(soMap.get(bookingNo)) == 0).collect(Collectors.toList()));
                // 判断是否提单
                if (blnoDos.stream().anyMatch(a -> Objects.equals(a.getBookingNo(), bookingNo))) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_SI_EXISTED.getMsg());
                }
                // 装货港不可为北方四港
                SoDO soDo = soList.stream().filter(a -> a.getId().compareTo(checkSo.getSoId())==0).findFirst().get();
                if (SEND_EDO_PORT.contains(soDo.getPolCode())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.WEB_SO_NOT_BEI_FANG_SAN_GANG.getMsg());
                }
                if (CNSHA.equals(soDo.getPolCode()) && !CNSHA.equals(soDo.getPorCode())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_NOT_CNSHA.getMsg());
                }
                if (CNNGB.equals(soDo.getPolCode()) && !CNNGB.equals(soDo.getPorCode()) &&
                        configuration.getTopAgent().contains(soDo.getTopAgent())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_NOT_CNNGB.getMsg());
                }
                if (!SoStatusEnum.BookingConfirmed.getValue().equals(soDo.getSoStatus())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_STATUS_ERROR.getMsg());
                }
                if(soAmendApplyBookingNos.contains(bookingNo)){
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_STATUS_AMEND.getMsg());
                }
                if (!checkCargo(soCargoDos,checkSo.getSoId())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_CARGO_TYPE_ERROR.getMsg());
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_DRY_ERROR.getMsg());
                }
                if (soMain.getRouteId().compareTo(soDo.getRouteId())!=0) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_ROUTE_SAME.getMsg());
                }
                if (!Objects.equals(soMain.getPorCode(),soDo.getPorCode())
                        || !Objects.equals(soMain.getPolCode(),soDo.getPolCode())
                        || !Objects.equals(soMain.getPodCode(),soDo.getPodCode())
                        || !Objects.equals(soMain.getDelCode(),soDo.getDelCode()) ) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_PORT_CODE_SAME.getMsg());
                }
                if (!Objects.equals(soMain.getBookingVessel()+soMain.getBookingVoyage(),
                        soDo.getBookingVessel()+soDo.getBookingVoyage())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_VESSEL_SAME.getMsg());
                }
                if (!Objects.equals(soMain.getBookingParty(),soDo.getBookingParty())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_BKP_CODE_SAME.getMsg());
                }
                if (!Objects.equals(soMain.getShippingTerm(),soDo.getShippingTerm())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_SHIP_SAME.getMsg());
                }
                if (!Objects.equals(soMain.getAgreementNo(),soDo.getAgreementNo())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_AGREEMENT_SAME.getMsg());
                }
                if (!Objects.equals(soMain.getSpaceOwner(),soDo.getSpaceOwner())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_SPACE_OWNER_SAME.getMsg());
                }
                if (!Objects.equals(soMain.getCntrStatus(),soDo.getCntrStatus())) {
                    messageList.add(BlnoSoCombineSiErrorCodeConstants.SO_LIST_CNTR_STATUS_SAME.getMsg());
                }
            }
            checkSo.setMessage(messageList);
            rntList.add(checkSo);
        });
        return rntList;
    }


    /**
     * 判断是否北方四港
     * @param polCode
     * @param porCode
     * @param topAgent
     * @return
     */
    @Override
    public boolean checkPortAgent(String polCode,String porCode,String topAgent){
        if (SEND_EDO_PORT.contains(polCode)) {
            return true;
        }
        if (CNSHA.equals(polCode) && !CNSHA.equals(porCode)) {
            return true;
        }
        if (CNNGB.equals(polCode) && !CNNGB.equals(porCode) &&
                configuration.getTopAgent().contains(topAgent)) {
            return true;
        }
        return false;
    }

    public boolean checkSoCargoIfHaveSpecial(SoDetailRespVO soDetail) {
        List<SoCargoRespVO> cargoList = soDetail.getSoCargoList();
        for (SoCargoRespVO e : cargoList) {
            if (SoCargoRespVO.checkSpecialCargo(e)) {
                return true;
            }
        }
        return false;
    }

    public boolean hasSiByBlNo(String blNo) {
        return blnoMapper.selectCount(BlnoDO::getBlNo, blNo) > 0;
    }

    private boolean checkCargo(List<SoCargoDO> soCargoDos,Long soId) {
        soCargoDos = soCargoDos.stream().filter(a -> a.getSoId().compareTo(soId) == 0).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(soCargoDos)) {
            if (soCargoDos.stream().anyMatch(n -> BlnoConstants.CARGO_TYPES.contains(n.getCargoType()))) {
                return false;
            }
            return soCargoDos.stream().noneMatch(n -> BlnoConstants.DRY.equals(n.getCargoType()) && StringUtils.isNotEmpty(n.getSubCargoType()));
        }
        return true;
    }

    @Override
    public List<SoForecastBaseVO> getSoForecast(List<Long> soIds){
        List<SoForecastDO> soForecastDos = soForecastMapper.selectList(new LambdaQueryWrapper<SoForecastDO>().in(SoForecastDO::getSoId, soIds));
        Map<String, Long> map = soForecastDos.stream()
                .collect(Collectors.groupingBy(
                        n -> n.getSoId()+"-"+n.getCntrType() + "-" + n.getCntrSize(),
                        Collectors.summingLong(SoForecastDO::getCntrQuantity)
                ));
        List<SoForecastDO> distinctSoForecastDos = new ArrayList<>(soForecastDos.stream()
                .collect(Collectors.toMap(
                        p -> p.getSoId()+"-"+p.getCntrType() + "_" + p.getCntrSize(), // 拼接key
                        Function.identity(),
                        (existing, replacement) -> existing // 保留第一个出现的
                ))
                .values());
        distinctSoForecastDos.forEach(n-> n.setCntrQuantity(map.get(n.getSoId()+"-"+n.getCntrType() + "-" + n.getCntrSize())));
        return BeanUtil.copyToList(distinctSoForecastDos,SoForecastBaseVO.class);
    }

    private SoPageReqDto convertSoPageReqDto(String csmCode,SoDO soDO){
        SoCargoDO soCargoDO = soCargoMapper.selectOne(new LambdaQueryWrapperX<SoCargoDO>().eq(SoCargoDO::getSoId,soDO.getId()));
        //船名航次、接货地、装货港、卸货港、目的地、订舱人、协议号、货类、运输条款、舱位代码、箱主、箱态必须一致
        SoPageReqDto soPageReqDto = new SoPageReqDto();
        soPageReqDto.setBookingVessel(soDO.getBookingVessel());
        soPageReqDto.setBookingVoyage(soDO.getBookingVoyage());
        soPageReqDto.setPolCode(soDO.getPolCode());
        soPageReqDto.setPorCode(soDO.getPorCode());
        soPageReqDto.setPodCode(soDO.getPodCode());
        soPageReqDto.setDelCode(soDO.getDelCode());
        soPageReqDto.setAgreementNo(soDO.getAgreementNo());
        soPageReqDto.setCntrOwner(soDO.getCntrOwner());
        soPageReqDto.setCntrStatus(soDO.getCntrStatus());
        soPageReqDto.setShippingTerm(soDO.getShippingTerm());
        soPageReqDto.setSpaceOwner(soDO.getSpaceOwner());
        soPageReqDto.setCargoType(soCargoDO.getCargoType());
        soPageReqDto.setDataSource(SoDataSourceEnum.WEB.getValue());
        soPageReqDto.setBookingParty(csmCode);
        soPageReqDto.setSoStatus(SoStatusEnum.BookingConfirmed.getValue());
        soPageReqDto.setPageNo(1);
        soPageReqDto.setPageSize(100);
        return soPageReqDto;
    }
}
